﻿using System;
using System.Collections.Generic;
using System.Diagnostics; // For Stopwatch
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace StopwatchApp.StateMachine
{
    /// <summary>
    /// Possible states the stopwatch state machine may be in.
    /// </summary>
    public enum StopwatchState
    {
        Empty,
        Paused,
        Timing
    }

    /// <summary>
    /// A class to aggregate data associated with a stopwatch.
    /// </summary>
    public class StopwatchMachine
    {
        //*** Constants ***//
        //*** Member Variables ***/
        private TimeSpan _FullBacklog;
        private DateTime _FullStartTime;

        private TimeSpan _LapBacklog;
        private DateTime _LapStartTime;

        private Action<StopwatchState> _OnStateChange;

        //*** Properties ***//
        public List<LapData> Laps { get; private set; }

        private StopwatchState _State;
        public StopwatchState State
        {
            get { return _State; }
            private set
            {
                _State = value;
                if (null != _OnStateChange)
                    _OnStateChange(_State);
            }
        }

        public TimeSpan FullDuration
        {
            get { return GetFullTime(); }
        }

        public TimeSpan LapDuration
        {
            get { return GetLapTime(); }
        }

        //*** Constructors ***//
        public StopwatchMachine(Action<StopwatchState> OnStateChange)
        {
            Laps = new List<LapData>();
            _OnStateChange = OnStateChange;
            State = StopwatchState.Empty;

            ResetStopwatch(); // Also sets default values.
        }

        //*** Methods ***//
        /// <summary>
        /// Starts the stopwatch.
        /// </summary>
        public void StartStopwatch()
        {
            _FullStartTime = _LapStartTime = DateTime.Now; // Set the same start times.
            State = StopwatchState.Timing;
        }

        /// <summary>
        /// Pauses to stop watch.  Stops timing both current lap and total time, but does not reset
        /// them or clear any associated lap data.
        /// </summary>
        public void PauseStopwatch()
        {
            // Add the segment of time we just timed into the backlog and reset the start time.
            var dtNow = DateTime.Now; // Use the same time for both the full and lap times.
            _FullBacklog += dtNow - _FullStartTime;
            _LapBacklog += dtNow - _LapStartTime;

            _FullStartTime = DateTime.MinValue;
            _LapStartTime = DateTime.MinValue;

            State = StopwatchState.Paused;
        }

        /// <summary>
        /// Resets the stopwatch.  Clears all laps associated with the stopwatch and sets the 
        /// elapsed time for both the current lap and total time to 0.
        /// </summary>
        public void ResetStopwatch()
        {
            _FullBacklog = new TimeSpan(0);
            _FullStartTime = DateTime.MinValue;

            _LapBacklog = new TimeSpan(0);
            _LapStartTime = DateTime.MinValue;

            Laps.Clear();

            State = StopwatchState.Empty;
        }

        /// <summary>
        /// Returns the total time associated with the stopwatch.
        /// </summary>
        /// <returns>TimeSpan indicating how long the stopwatch has been running.</returns>
        private TimeSpan GetFullTime()
        {
            return GetTotalTime(_FullStartTime, _FullBacklog);
        }

        /// <summary>
        /// Returns the total time associated with the current lap on the stopwatch.
        /// </summary>
        /// <returns>TimeSpan indicating how long the current lap has been running.</returns>
        private TimeSpan GetLapTime()
        {
            return GetTotalTime(_LapStartTime, _LapBacklog);
        }

        /// <summary>
        /// Calculates the total time of a "stopwatch" by taking the current segment and adding
        /// the backlog to it.  If the current segment doesn't exist the total time is the time
        /// captured in the backlog.
        /// </summary>
        /// <param name="aStartTime">The starting time of the current segment.</param>
        /// <param name="aBacklog">The total backlog time from other time segments.</param>
        /// <returns>Total time associated with the stopwatch.</returns>
        private TimeSpan GetTotalTime(DateTime aStartTime, TimeSpan aBacklog)
        {
            if (DateTime.MinValue != aStartTime)
                return (DateTime.Now - aStartTime) + aBacklog;
            return aBacklog;
        }

        /// <summary>
        /// Finishes timing the old lap and starts timing the new lap.
        /// </summary>
        public void LapStopwatch()
        {
            var dtNow = DateTime.Now; // Use the same "now" throughout the method.

            // Add the current segment to the backlog, if it exists.  Use the total time aggregated
            // in the backlog as the time for the lap.
            if (DateTime.MinValue != _LapStartTime)
            {
                _LapBacklog += dtNow - _LapStartTime;
            }

            Laps.Add(new LapData(Laps.Count, _LapBacklog));

            // Always reset the backlog time.
            _LapBacklog = new TimeSpan(0);

            // Restart the lap timer if the stopwatch is currently timing.
            if (StopwatchState.Timing == State)
                _LapStartTime = dtNow;
            else
                _LapStartTime = DateTime.MinValue;

        }
    }
}
